package com.jeasonzhao.model;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.util.Collection;

import org.apache.commons.beanutils.PropertyUtils;
import com.jeasonzhao.commons.utils.Algorithms;
import com.jeasonzhao.commons.utils.DataTypes;
import javax.persistence.Id;
import javax.persistence.Entity;
import javax.persistence.Column;
import javax.persistence.MappedSuperclass;
import com.jeasonzhao.commons.annotation.Title;

public class DefaultModelMetaGenerator implements IModelMetaDataGenerator
{
    public DefaultModelMetaGenerator()
    {
        super();
    }

    public MappedInformation generateClassInformation(Class<?> mainClass)
        throws ModelException
    {
        checkMainClass(mainClass);
        Class<?> orginal = mainClass;
        if(mainClass.getSimpleName().indexOf("$EnhancerByCGLIB$") >= 0)
        {
            mainClass = mainClass.getSuperclass();
        }
//        System.out.println("DefaultModelMetaGenerator>>" + "  fromAnnotation: " + mainClass.getName());
        MappedInformation ret = this.fromAnnotation(mainClass);
        Title desc=mainClass.getAnnotation(Title.class);
        if(null==desc)
        {
            desc=orginal.getAnnotation(Title.class);
        }
        if(null!=desc)
        {
            ret.setTitle(desc.value());
        }
        else
        {
            ret.setTitle(mainClass.getName());
        }
        if(null == ret || ret.isEmpty())
        {
//            System.out.println("DefaultModelMetaGenerator>>" + "  fromHibernateConfig: " + mainClass.getName());
            ret = this.fromHibernateConfig(mainClass);
        }
        if(null == ret || ret.isEmpty())
        {
//            System.out.println("DefaultModelMetaGenerator>>" + "  fromPojo: " + mainClass.getName());
            ret = this.fromPojo(mainClass);
        }
        if(null != ret && ret.isEmpty() == false)
        {
//            System.out.println("DefaultModelMetaGenerator>>" + ret.getSourceType() + ": " + mainClass.getName());
//            for(MappedFieldItem item : ret.getMappedItems())
//            {
//                String appendix = "";
//                if(item.getAppendix() != null)
//                {
//                    appendix = "[dict:" + item.getAppendix().isDictionaryNode()
//                        + ";title:" + item.getAppendix().getTitle()
//                        + ";nodeType:" + item.getAppendix().getTreeNodeType()
//                        + "]";
//                }
//                System.out.println("\t>>" + item.getName() + ">" + item.getType() + " " + appendix);
//            }
        }
        else
        {
            ret.setOriginalClass(orginal);
        }
        return ret == null || ret.isEmpty() ? null : ret;
    }

    private void checkMainClass(Class<?> mainClass)
        throws ModelConfigurationException
    {
        if(null == mainClass)
        {
            throw new ModelConfigurationException("Parameter Error: Null pointer parameter.");
        }
        DataTypes dt = DataTypes.from(mainClass);
        if(dt.isArray())
        {
            throw new ModelConfigurationException(mainClass.getName() + " is an Array class, "
                                                  + "no model could be mapped to it.");
        }
        else if(dt.isPrimitive()
                || dt.equals(DataTypes.VOID)
                || mainClass.equals(Object.class))
        {
            throw new ModelConfigurationException(mainClass.getName() + " is a Primitive class, "
                                                  + "no model could be mapped to it.");
        }
    }

    private MappedInformation fromAnnotation(Class<?> mainClass)
        throws ModelException
    {
        MappedInformation ret = new MappedInformation(mainClass,MappedInformationSourceType.ModelAnnotation);
        for(Class<?> clsT : getClassHierarchy(mainClass))
        {
            Model annModel = (Model) mainClass.getAnnotation(Model.class);
            if(null != annModel) //It could be override by sub-classes.
            {
                if(annModel.cleanParent())
                {
                    ret.clean();
                }
            }
            for(Field field : clsT.getDeclaredFields())
            {
                if(field.getAnnotation(ModelField.class) != null)
                {
                    fielMappingFromAnnotation(ret,field);
                }
            }
            if(null != annModel) //It could be overrided by sub-classes.
            {
                ret.setName(annModel.value());
                String pk = annModel.primaryKey();
                if(Algorithms.isEmpty(pk) == false)
                {
                    ret.cleanPrimaryKeys();
                    for(String s : pk.split(","))
                    {
                        MappedFieldItem field = ret.getFieldMap(s);
                        if(null != field)
                        {
                            field.setPrimaryKey(true);
                        }
                    }
                }
            }
        }
        return ret;
    }

    private MappedFieldItem fielMappingFromAnnotation(MappedInformation mapInfo,Field field)
        throws ModelException
    {
        ModelField annotation = null == field ? null : field.getAnnotation(ModelField.class);
        if(null == annotation)
        {
            return null;
        }
        else
        {
            MappedFieldItem item = new MappedFieldItem(mapInfo,field);
            String names = (annotation.value() == null ? "" : annotation.value()) + "," +
                (annotation.names() == null ? "" : annotation.names());
            if(Algorithms.isEmpty(names) == false)
            {
                String[] allNames = names.split(",");
                java.util.HashSet<String> set = new java.util.HashSet<String>();
                for(String s : allNames)
                {
                    s = s.trim();
                    if(s.length() < 1 || set.contains(s.toLowerCase()))
                    {
                        continue;
                    }
                    set.add(s.toLowerCase());
                    if(set.size() == 0)
                    {
                        item.setName(s);
                    }
                    item.addName(s);
                }
            }
            item.setComponentClass(guessCollectionClass(field));
            item.setCdata(annotation.cdata());
            item.setNewNode(annotation.forceNode());
            item.setPrimaryKey(annotation.primaryKey() || null != field.getAnnotation(Id.class));
            mapInfo.addFiledMap(item);
            return item;
        }
    }

    private MappedInformation fromPojo(Class<?> mainClass)
        throws ModelException
    {
        MappedInformation ret = new MappedInformation(mainClass,MappedInformationSourceType.POJO);
        PropertyDescriptor[] pds = PropertyUtils.getPropertyDescriptors(mainClass);
        for(PropertyDescriptor p : pds)
        {
            if(p.getName().equalsIgnoreCase("class"))
            {
                continue;
            }
            if(p.getReadMethod() != null) // && p.getWriteMethod() != null)
            {
                MappedFieldItem item = MappedFieldItem.pojoField(ret,p.getName(),p.getPropertyType());
                if(item != null)
                {
                    item.setComponentClass(guessCollectionClass(item.getField()));
                }
                ret.addFiledMap(item);
            }
        }
        return ret;
    }

    private MappedInformation fromHibernateConfig(Class<?> cls)
        throws ModelException
    {
//        System.out.println("DefaultModelMetaGenerator>>" + "  fromHibernateConfig: checking " + cls.getName());
        Entity e = cls.getAnnotation(Entity.class);
        javax.persistence.Table t = cls.getAnnotation(javax.persistence.Table.class);
        if(null == t && null == e)
        {
            return null;
        }
//        System.out.println("DefaultModelMetaGenerator>>" + "  fromHibernateConfig: e " + cls.getName());
        MappedInformation ret = new MappedInformation(cls,MappedInformationSourceType.Hibernate);
        ret.setName(Algorithms.oneOfNotNull(null == t ? null : t.name(),null == e ? null : e.name()));
        java.util.Vector<Class<?>> classes = new java.util.Vector<Class<?>>();
        classes.add(cls);
        Class<?> clst = cls.getSuperclass();
        while(null != clst)
        {
            if(clst.equals(Object.class) || clst.equals(Class.class))
            {
                break;
            }
            else if(clst.getAnnotation(MappedSuperclass.class) != null)
            {
                classes.insertElementAt(clst,0);
                clst = clst.getSuperclass();
            }
            else
            {
                break;
            }
        }
//        System.out.println("DefaultModelMetaGenerator>>" + "  fromHibernateConfig: e2 " + cls.getName());
        for(Class<?> c : classes)
        {
            for(Field f : c.getDeclaredFields())
            {
                Id id = f.getAnnotation(Id.class);
                Column col = f.getAnnotation(Column.class);
                if(null == id && null == col)
                {
                    continue;
                }
                MappedFieldItem m = new MappedFieldItem(ret,f);
                if(m != null)
                {
                    m.setComponentClass(guessCollectionClass(m.getField()));
                }
                m.setNullable(null==col?true:col.nullable());
                m.addName(null == col ? null : col.name());
                m.setPrimaryKey(null != id);
                ret.addFiledMap(m);
            }
        }
        return ret;
    }

    private static Class<?> guessCollectionClass(Field field)
        throws ModelException
    {
        if(null == field)
        {
            return null;
        }
        Class<?> clsRet = null;
        ModelField annotation = field.getAnnotation(ModelField.class);
        if(null != annotation && annotation.collection() == false)
        {
            return null;
        }
        if(false == Collection.class.isAssignableFrom(field.getType()))
        {
            if(null != annotation)
            {
                throw new ModelException("The collection node " +
                                         field.getName() + " typed " + field.getName() +
                                         " is not dervired from " + Collection.class.getName());
            }
        }
        String strClassName = null == annotation ? null : annotation.component();
        if(Algorithms.isEmpty(strClassName))
        {
            strClassName = guessCollectionClassName(field,strClassName);
        }
        if(null == strClassName)
        {
            if(null != annotation)
            {
                throw new ModelException("Can not found component class name for " +
                                         field.getType().getName());
            }
            else
            {
                return null;
            }
        }
        try
        {
            clsRet = Class.forName(strClassName);
            return clsRet;
        }
        catch(ClassNotFoundException ex)
        {
        }
        if(strClassName.indexOf(".") < 0)
        {
            try
            {
                strClassName = field.getDeclaringClass().getPackage().getName() + "." + strClassName;
                clsRet = Class.forName(strClassName);
                return clsRet;
            }
            catch(ClassNotFoundException ex)
            {
            }
        }
        if(null != annotation)
        {
            throw new ModelException("Can not load class named " + strClassName);
        }
        else
        {
            return null;
        }
    }

    private static String guessCollectionClassName(Field field,String strClassName)
    {
        strClassName = ModelHelper.getInstance().getComponentClassFromCollectionClass(field.getType().getName());
        if(null == strClassName)
        {
            if(field.getType().getName().toLowerCase().endsWith("Collection".toLowerCase()))
            {
                strClassName = field.getType().getName().substring(0,field.getType().getName().length() - "Collection".length());
            }
            else if(field.getType().getName().toLowerCase().endsWith("Vector".toLowerCase()))
            {
                strClassName = field.getType().getName().substring(0,field.getType().getName().length() - "Vector".length());
            }
            else if(field.getType().getName().toLowerCase().endsWith("Array".toLowerCase()))
            {
                strClassName = field.getType().getName().substring(0,field.getType().getName().length() - "Array".length());
            }
            else if(field.getType().getName().toLowerCase().endsWith("ArrayList".toLowerCase()))
            {
                strClassName = field.getType().getName().substring(0,field.getType().getName().length() - "ArrayList".length());
            }
            else if(field.getType().getName().toLowerCase().endsWith("List".toLowerCase()))
            {
                strClassName = field.getType().getName().substring(0,field.getType().getName().length() - "List".length());
            }
            if(null != strClassName)
            {
                Class<?> clsTemp = null;
                try
                {
                    clsTemp = Class.forName(strClassName);
                }
                catch(ClassNotFoundException ex)
                {
                }
                if(null != clsTemp)
                {
                    ModelHelper.getInstance().registerCollectionSupport(field.getType(),clsTemp);
                }
                else
                {
                    strClassName = null;
                }
            }
        } //end null==strClassName
        return strClassName;
    }

    @SuppressWarnings("rawtypes")
    public static java.util.Stack<Class> getClassHierarchy(Class<?> cls)
        throws ModelException
    {
        try
        {
            if(null == cls)
            {
                throw new ModelConfigurationException("Can not build a instance from a null pointer");
            }
            if(cls.isAnnotation() || cls.isAnonymousClass()
               || cls.isArray() || cls.isEnum() || cls.isInterface()
               || cls.isPrimitive()
                )
            {
                throw new ModelConfigurationException("Can not build instance from a non-normal class:" + cls.getName());
            }
            if(java.lang.reflect.Modifier.isAbstract(cls.getModifiers()))
            {
                throw new ModelConfigurationException("The class is abstract :" + cls.getName());
            }
            if(cls.getConstructor() == null)
            {
                throw new ModelConfigurationException("There is no default constructor for class " + cls.getName());
            }
            java.util.Stack<Class> hierarchy = new java.util.Stack<Class>();
            hierarchy.push(cls);
            while(true)
            {
                Class<?> clsP = cls.getSuperclass();
                if(null == clsP || clsP.equals(Object.class) || clsP.equals(Class.class))
                {
                    break;
                }
                else
                {
                    hierarchy.push(clsP);
                }
                cls = clsP;
            }
            return hierarchy;
        }
        catch(SecurityException ex)
        {
            throw new ModelConfigurationException("Building hierarchy for class " + cls.getName()
                                                  + " error: " + ex.getMessage(),ex);
        }
        catch(NoSuchMethodException ex)
        {
            throw new ModelConfigurationException("Building hierarchy for class " + cls.getName()
                                                  + " error: " + ex.getMessage(),ex);
        }
    }
}
